package main.threading;

import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.jmex.game.state.GameState;
import com.jmex.game.state.GameStateManager;

/**
 * This class serves as a workaround for clean shutdown of
 * <code>StandardGame</code> when other threads are working. At least the
 * main-Thread which can be considered the initialization thread of a game
 * application could still run if the OpenGL thread stops. It is recommended to
 * add a <code>ShutdownListener</code> to the <code>GameShutdownManager</code>
 * for every game that uses the <code>GameTaskQueueManager</code> to make sure
 * the threads do not lock on a future that will never occur.
 * <p>
 * Please note, that an internal game state is created and attached to the
 * <code>GameStateManager</code> if listeners are added. Also note that this
 * class is thread safe.
 * 
 * @author Sven Pohl
 */
public class GameShutdownManager {
  private static final Logger logger = Logger.getLogger(GameShutdownManager.class.getName());

  private static final GameShutdownManager INSTANCE = new GameShutdownManager();

  private GameShutdownManager() {
  }

  public static GameShutdownManager get() {
    return INSTANCE;
  }

  private ConcurrentLinkedQueue<ShutdownListener> queue = new ConcurrentLinkedQueue<ShutdownListener>();

  private ShutdownObserverGamestate gameState;

  public synchronized void addListener(ShutdownListener listener) {
    if (queue.isEmpty()) {
      gameState = new ShutdownObserverGamestate();
      GameStateManager.getInstance().attachChild(gameState);
    }
    queue.add(listener);
  }

  public synchronized void removeListener(ShutdownListener listener) {
    queue.remove(listener);
    if (queue.isEmpty()) {
      gameState = new ShutdownObserverGamestate();
      GameStateManager.getInstance().detachChild(gameState);
    }
  }

  public interface ShutdownListener {
    void onShutdown();
  }

  private class ShutdownObserverGamestate extends GameState {
	@Override
    public void cleanup() {
      for (Iterator<ShutdownListener> i = queue.iterator(); i.hasNext();) {
        ShutdownListener listener = i.next();
        listener.onShutdown();
      }
    }

    /** no need to update anything */
	@Override
    public void render(float tpf) {
    }

    /** no need to update anything */
	@Override
    public void update(float tpf) {
    }
  }

  public ShutdownListener addThreadInterrupter(final Thread thread) {
    ShutdownListener result = new ShutdownListener() {

      @Override
	public void onShutdown() {
        if (thread.isAlive())
          thread.interrupt();
      }

    };
    addListener(result);
    return result;
  }

  /**
   * Convenience method to add a ShutdownListener, which executes a
   * System.exit(0) after the game (OpenGL) thread has finished.
   * 
   * @param maxDelayMillis
   *          the maximum delay in milliseconds of the System.exit(0) call. If
   *          the OpenGL thread did not finish then, System.exit(0) is called. 0
   *          means no delay at all, which can lead to java virtual machine
   *          crash.
   * @return the created ShutdownListener
   */
  public ShutdownListener addSystemExitWithMaxDelay(final int maxDelayMillis) {
    ShutdownListener result = new ShutdownListener() {

      @Override
	public void onShutdown() {
        // TODO is this assumption reliable?
        final Thread openglThread = Thread.currentThread();
        if (maxDelayMillis <= 0)
          System.exit(0);

        try {
          new Thread(new Runnable() {

            @Override
			public void run() {
              long startMillis = System.currentTimeMillis();
              while (openglThread.isAlive()
                  && System.currentTimeMillis() < startMillis + maxDelayMillis) {
                try {
                  Thread.sleep(50);
                } catch (InterruptedException e) {
                  logger.warning("delay of system exit interrupted");
                }
              }
              System.exit(0);
            }
          }).start();
        } catch (Throwable e) {
          logger.log(Level.WARNING, "could not start thread to call a delayed "
              + "System.exit(0). exiting instantly.", e);
          System.exit(0);
        }
      }

    };
    addListener(result);
    return result;
  }
}